﻿
using PSE.Framework.Communication.Configuration.Adapters.XmlHttp;
using PSE.Framework.Communication.Adapters;
using PSE.Framework.Communication.Configuration.Adapters;
using PSE.Framework.Communication.Messages;
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.IO;
using System.Globalization;
using System.Net;
using System.Xml;
using PSE.Framework.Communication.Configuration.Exceptions;
using System.Text;
using PSE.Framework.Communication.Configuration.XmlHttp;

namespace PSE.Framework.Communication.Adapters.XmlHttp 
{
    public class XmlHttpInputAdapter : SyncInputAdapter<XmlHttpInputConfiguration, object, XmlHttpRequestParameter>
    {
        private Dictionary<string, XmlSerializer> _serializers;

        public XmlHttpInputAdapter()
        {

        }

        public override void Open(XmlHttpInputConfiguration config, object parameter)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            this.VerifyIsCreated();

            _serializers = new Dictionary<string, XmlSerializer>();

            // Criando serializers
            foreach (XmlHttpInputAdapterActionConfiguration action in config.Actions)
            {
                if (action.DeserializedMessageType != null)
                {
                    _serializers.Add(action.Name, new XmlSerializer(action.DeserializedMessageType));
                }
            }

            base.Open(config, parameter);
        }

        public override MessageInfo Receive(XmlHttpRequestParameter parameter)
        {
            this.VerifyIsOpened();

            string message = null;

            // Capturado url e credenciais do arquivo de configuração
            Uri url = this.Configuration.Url;
            ICredentials credentials = this.Configuration.Credentials.GetCredential();
            ICredentials proxyCredentials = this.Configuration.ProxyCredentials.GetCredential();


            // Verifica se uma url foi informada no arquivo de configuração ou via parâmetro
            if (url == null && (parameter == null || parameter.Url == null))
                throw new InvalidAdapterConfigurationException("The URL must be informed through configuration or parameter", this.Configuration.Name);


            // Verifica se foi utilizado um parâmetro
            if (parameter != null)
            {
                // Verifica se credenciais foram informadas
                if (parameter.Credentials != null)
                    credentials = parameter.Credentials;


                // Verifica se credenciais do proxy foram informadas
                if (parameter.ProxyCredentials != null)
                    proxyCredentials = parameter.ProxyCredentials;


                // Verifica se um path foi informado
                if (parameter.Url != null)
                {
                    // Verifica se um path foi informado no arquivo de configuração
                    if (url != null)
                    {
                        if (url.IsAbsoluteUri && !parameter.Url.IsAbsoluteUri)
                            // Path do arquivo é absoluto e do parâmetro é relativo, 
                            // utilizar o do arquivo como base
                            url = new Uri(url, parameter.Url);

                        else if (!url.IsAbsoluteUri && parameter.Url.IsAbsoluteUri)
                            // Path do arquivo é relativo e do parâmetro é absoluto, 
                            // utilizar o do parâmetro como base
                            url = new Uri(parameter.Url, url);

                        else
                            // Path do arquivo e do parâmetro são absolutos ou relativos, 
                            // utilizar path do parâmetro
                            url = parameter.Url;
                    }
                    else
                    {
                        // Path não foi informado no arquivo de configuração, utilizar o do parâmetro
                        url = parameter.Url;
                    }
                }


                // Verifica se a query string foi informada
                if (parameter.Query.Count > 0)
	            {
                    UriBuilder uriBuilder = new UriBuilder(url);

                    int count = 0;
                    StringBuilder query = new StringBuilder();

                    // Verifica se já existia uma query string na url
                    if (!string.IsNullOrEmpty(uriBuilder.Query))
                    {
                        // Se existir adiciona no string builder

                        // Removendo "?" do inicio da query string
                        if (uriBuilder.Query.StartsWith("?"))
                            query.Append(uriBuilder.Query, 1, uriBuilder.Query.Length - 1);
                        else
                            query.Append(uriBuilder.Query);

                        // Adicionando "&" do fim da query string
                        if (!uriBuilder.Query.EndsWith("&"))
                            query.Append("&");
                    }

                    foreach (KeyValuePair<string, object> p in parameter.Query)
                    {
                        query.AppendFormat("{0}={1}", 
                            p.Key,
                            // Formata datas com SortableDateTimePattern (string format "s") que é yyyy-MM-ddTHH:mm:ss, 
                            // isso porque o InvariantCulture por padrão formata as datas no padrão en-US
                            Uri.EscapeDataString(string.Format(CultureInfo.InvariantCulture, "{0" + (p.Value is DateTime ? ":" + DateTimeFormatInfo.InvariantInfo.SortableDateTimePattern : string.Empty) + "}", p.Value)));

                        count++;
                        if (count < parameter.Query.Count)
                            query.Append("&");
                    }

                    // Verifica e remove "/" do final do path
                    if (uriBuilder.Path.EndsWith("/"))
                        uriBuilder.Path = uriBuilder.Path.Substring(0, uriBuilder.Path.Length - 1);

                    uriBuilder.Query = query.ToString();
                    url = uriBuilder.Uri;
            	}
            }

            if (!Uri.IsWellFormedUriString(url.ToString(), UriKind.RelativeOrAbsolute))
                url = new Uri(Uri.EscapeUriString(url.ToString()));
            

            using (WebClient webClient = new WebClient())
            {
                if (credentials != null)
                    webClient.Credentials = credentials;

                if (proxyCredentials != null)
                    webClient.Proxy.Credentials = proxyCredentials;

                message = webClient.DownloadString(url);
            }

            if (string.IsNullOrEmpty(message))
                throw new AdapterException("The returned message is empty", this.Configuration.Name, AdapterDirection.Input);

            return this.ProcessAndDeliveryMessage(message);
        }

        protected override object Deserialize(object message, string action)
        {
            object deserializedMessage = message;
            StringReader reader;

            // Validando a mensagem
            if (!string.IsNullOrEmpty(this.Configuration.Actions[action].ValidationFile))
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.Schemas.Add(null, this.Configuration.Actions[action].ValidationFile);
                settings.ValidationType = ValidationType.Schema;

                reader = new StringReader(System.Convert.ToString(message));

                // Percorre toda a mensagem para validar
                using (XmlReader xmlReader = XmlReader.Create(reader, settings))
                {
                    while (xmlReader.Read()) { }
                }
            }

            // Se não tiver um type configurado retorna a string xml
            if (_serializers.ContainsKey(action))
            {
                reader = new StringReader(System.Convert.ToString(message));

                deserializedMessage = _serializers[action].Deserialize(reader);
            }

            return deserializedMessage;
        }
    }

}